Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new Kubernetes Ingress primitive to the operator component framework, following the existing primitive patterns (builder/resource + mutator pipeline + flavors + lifecycle handlers), and includes an accompanying example and documentation.
Changes:
- Introduces
pkg/primitives/ingress(builder/resource, mutator, handlers, flavors) plus unit tests. - Adds a shared
IngressSpecEditorunderpkg/mutation/editors(with tests) to support typed ingress spec mutations. - Adds an
examples/ingress-primitivewalkthrough and newdocs/primitives/ingress.mddocumentation.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/primitives/ingress/resource.go | Defines the ingress Resource wrapper over generic.IntegrationResource. |
| pkg/primitives/ingress/builder.go | Builder API wiring for applicators, flavors, mutations, status/suspension handlers, data extraction. |
| pkg/primitives/ingress/mutator.go | Plan/apply mutator with per-feature planning and category ordering. |
| pkg/primitives/ingress/handlers.go | Default operational/suspension handler implementations for integration lifecycle. |
| pkg/primitives/ingress/flavors.go | Ingress flavors (label/annotation preservation) via shared pkg/flavors. |
| pkg/primitives/ingress/*_test.go | Unit tests for ingress builder/mutator/handlers/flavors behavior. |
| pkg/mutation/editors/ingressspec.go | Adds typed IngressSpecEditor for ingress spec mutations. |
| pkg/mutation/editors/ingressspec_test.go | Tests for IngressSpecEditor operations (class name, backend, rules, TLS). |
| examples/ingress-primitive/main.go | Standalone example driving reconciliation steps using a fake client. |
| examples/ingress-primitive/resources/ingress.go | Example ingress resource factory using mutations, flavors, and data extraction. |
| examples/ingress-primitive/features/mutations.go | Example feature-gated ingress mutations (version annotation + TLS). |
| examples/ingress-primitive/app/controller.go | Example controller that builds a component around the ingress primitive. |
| examples/ingress-primitive/app/owner.go | Re-exports shared ExampleApp types for the example package. |
| examples/ingress-primitive/README.md | Documents how to run and understand the ingress example. |
| docs/primitives/ingress.md | New primitive documentation covering usage, ordering, handlers, flavors, guidance. |
Claude Review Cycle 1 CompleteAddressed:
Intentionally ignored:
<!-- claude-review-cycle --> |
|
approved |
c21b61c to
c31fd20
Compare
Claude Review Cycle 1 CompleteAddressed:
Intentionally ignored:
<!-- claude-review-cycle --> |
Provides SetIngressClassName, SetDefaultBackend, EnsureRule (upsert by host), RemoveRule, EnsureTLS (upsert by first host), RemoveTLS, and Raw escape hatch. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements builder, resource, mutator, handlers, and flavors for the networking.k8s.io/v1 Ingress kind. Key design decisions: - DefaultDeleteOnSuspend = false (avoids ingress controller churn) - DefaultSuspendMutation = no-op (backend 502/503 is correct behaviour) - Operational status: Pending until load balancer address assigned - Suspension status: immediately Suspended with reason message Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covers operational status, suspension strategy, mutation pipeline, editors, flavors, and guidance for common use cases. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Demonstrates base construction, feature mutations (version annotation, TLS toggle), field flavors, and data extraction using the ingress builder and mutator. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address Copilot review: DefaultOperationalStatusHandler now checks that at least one LoadBalancer.Ingress entry has a non-empty IP or Hostname, rather than only checking slice length. This prevents marking an Ingress as Operational when entries exist but have no assigned address. Update docs and add test for the empty-entry edge case. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix doc comments to reference concepts.Operational/Suspendable/DataExtractable instead of component.* for lifecycle interfaces - Use correct OperationPending status name in docs and code comments - Add ingress entry to built-in primitives table in docs/primitives.md - Add IngressSpecEditor to mutation editors table in docs/primitives.md - Add cross-reference link to primitives overview in ingress doc - Add resource_test.go with tests for Identity, Object deep-copy, Mutate, mutations, feature ordering, custom applicator, converging status, suspension, and data extraction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix doc comments to reference concepts.Operational/Suspendable/DataExtractable instead of component.* for lifecycle interfaces - Use correct OperationPending status name in docs and code comments - Add ingress entry to built-in primitives table in docs/primitives.md - Add IngressSpecEditor to mutation editors table in docs/primitives.md - Add cross-reference link to primitives overview in ingress doc - Add resource_test.go with tests for Identity, Object deep-copy, Mutate, mutations, feature ordering, custom applicator, converging status, suspension, and data extraction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The DefaultFieldApplicator was replacing the entire object with the desired state, which wiped ingress-controller-owned fields like Status.LoadBalancer.Ingress. This caused DefaultOperationalStatusHandler to never see assigned addresses, keeping the resource permanently pending. Save and restore the live Status before overwriting with the desired spec so that readiness detection works correctly. Also adds test coverage for: - Status.LoadBalancer preservation through DefaultFieldApplicator - DefaultOperationalStatusHandler returning Operational with IP - DefaultOperationalStatusHandler returning Operational with Hostname Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| func (m *Mutator) EditObjectMetadata(edit func(*editors.ObjectMetaEditor) error) { | ||
| if edit == nil { | ||
| return | ||
| } | ||
| m.active.metadataEdits = append(m.active.metadataEdits, edit) | ||
| } |
There was a problem hiding this comment.
EditObjectMetadata (and similarly EditIngressSpec) will panic with a nil-pointer dereference if BeginFeature() hasn’t been called, because m.active is nil. Since these methods don’t return an error, consider making them safe by lazily calling BeginFeature() when m.active == nil (or initializing a default feature plan in NewMutator) so accidental misuse can’t crash reconciliation.
| func (b *Builder) WithDataExtractor(extractor func(networkingv1.Ingress) error) *Builder { | ||
| if extractor != nil { | ||
| b.base.WithDataExtractor(func(ing *networkingv1.Ingress) error { | ||
| return extractor(*ing) | ||
| }) | ||
| } | ||
| return b | ||
| } |
There was a problem hiding this comment.
The docstring says the extractor receives a “value copy”, but extractor(*ing) is only a shallow struct copy (maps/slices/pointers still alias the same underlying data). If the intent is to prevent extractors from mutating the reconciled object (even accidentally), pass a deep copy (e.g., call ing.DeepCopy() before dereferencing) so nested fields can’t be mutated through shared references.
| networkingv1 "k8s.io/api/networking/v1" | ||
| ) | ||
|
|
||
| // IngressSpecEditor provides a typed API for mutating a Kubernetes IngressSpec. |
There was a problem hiding this comment.
The PR description checklist states “Does not modify shared files”, but this PR adds/changes shared framework code under pkg/mutation/editors/ (and also updates shared docs like docs/primitives.md). Either update the PR description/checklist to reflect that shared files are modified, or split the editor/doc changes into their own PR if “no shared file modifications” is a hard requirement.
|
|
||
| | Capability | Detail | | ||
| | ---------------------- | ---------------------------------------------------------------------------------------------- | | ||
| | **Operational status** | Reports `OperationPending` until the ingress controller assigns an address, then `Operational` | |
There was a problem hiding this comment.
The docs reference OperationPending, but the code returns concepts.OperationalStatusPending and the status concept elsewhere is “Pending” vs “Operational”. To avoid confusing API users, align terminology with the actual status names used by the framework (e.g., “Pending”/“Operational”, or explicitly OperationalStatusPending/OperationalStatusOperational).
| | **Operational status** | Reports `OperationPending` until the ingress controller assigns an address, then `Operational` | | |
| | **Operational status** | Reports `Pending` until the ingress controller assigns an address, then `Operational` | |
| if edit == nil { | ||
| return | ||
| } | ||
| m.active.ingressSpecEdits = append(m.active.ingressSpecEdits, edit) |
There was a problem hiding this comment.
EditObjectMetadata/EditIngressSpec will panic if BeginFeature() was not called (m.active is nil). Since Mutator is a public helper, it should fail safely. Consider implicitly calling BeginFeature() when m.active is nil, or changing these methods to return an error when no active feature plan exists (and have callers handle it).
| | Condition | Status | Reason | | ||
| | ----------------------------------------- | ------------------ | ----------------------------------------- | | ||
| | Entry with `IP != ""` or `Hostname != ""` | `Operational` | Ingress has been assigned an address | | ||
| | Otherwise | `OperationPending` | Awaiting load balancer address assignment | |
There was a problem hiding this comment.
The docs refer to OperationPending, but the implementation (and other docs/tests) use concepts.OperationalStatusPending / 'Pending' terminology. Please align the documentation to the actual status enum names to avoid confusion for API consumers.
Both methods now call BeginFeature() when no active feature plan exists, preventing nil-pointer panics when callers omit BeginFeature(). This preserves the ObjectMutator interface contract (no error return) while making the Mutator safe for public use without requiring strict call ordering. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude Review Cycle 1 CompleteAddressed:
Intentionally ignored:
<!-- claude-review-cycle --> |
Implements the
ingressKubernetes resource primitive following the pattern established by the existingConfigMapandDeploymentprimitives.Summary
ingressprimitive package underpkg/primitives/ingress/Checklist